package cn.kdyzm.minio.service;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.kdyzm.minio.client.DownloadMinioClient;
import cn.kdyzm.minio.client.UploadMinioClient;
import cn.kdyzm.minio.dto.UploadUrlParam;
import io.minio.*;
import cn.kdyzm.minio.MinioProperties;
import io.minio.errors.ErrorResponseException;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * @author kdyzm
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class MinioObjectService {

    private final UploadMinioClient uploadMinioClient;

    private final DownloadMinioClient downloadMinioClient;

    private final MinioProperties minioProperties;

    /**
     * 通过InputStream上传对象。
     *
     * @param bucketName String	存储桶名称。
     * @param objectName String	存储桶里的对象名称。
     */
    public void putObject(String bucketName, String path, String objectName, MultipartFile file) {
        try {
            InputStream in = file.getInputStream();
            uploadMinioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(path + "/" + objectName)
                            .stream(in, in.available(), -1)
                            .contentType(file.getContentType())
                            .build()
            );
            in.close();
        } catch (Exception e) {
            log.error("", e);
        }
    }

    /**
     * 传入字节流
     *
     * @param bucketName
     * @param path
     * @param objectName
     * @param inBytes
     * @param contentType
     */
    public void putObject(String bucketName, String path, String objectName, byte[] inBytes, String... contentType) {
        try {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inBytes);
            PutObjectArgs.Builder builder = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(path + "/" + objectName)
                    .stream(byteArrayInputStream, byteArrayInputStream.available(), -1);
            if (contentType.length >= 1) {
                builder.contentType(contentType[0]);
            }
            PutObjectArgs build = builder.build();
            uploadMinioClient.putObject(
                    build
            );
            IoUtil.close(byteArrayInputStream);
        } catch (Exception e) {
            log.error("", e);
        }
    }

    /**
     * 获取对象的元数据。
     *
     * @param bucketName String	存储桶名称。
     * @param objectName String	存储桶里的对象名称。
     * @return
     */
    public StatObjectResponse statObject(String bucketName, String objectName) throws FileNotFoundException {
        try {
            return uploadMinioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (ErrorResponseException e) {
            log.error("", e);
            String code = e.errorResponse().code();
            if ("NoSuchKey".equalsIgnoreCase(code)) {
                throw new FileNotFoundException("文件不存在");
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return null;
    }


    /**
     * 删除一个对象。
     *
     * @param bucketName String	存储桶名称。
     * @param objectName String	存储桶里的对象名称。
     */
    public void removeObject(String bucketName, String objectName) {
        try {
            uploadMinioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            log.error("", e);
        }
    }

    /**
     * 生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载，即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间，默认值是7天。
     *
     * @param bucketName String	存储桶名称。
     * @param objectName String	存储桶里的对象名称。
     * @param expires    Integer	失效时间（以秒为单位），默认是7天，不得大于七天。
     */
    public String presignedGetObject(String bucketName, String objectName, Integer expires) throws FileNotFoundException {
        this.statObject(bucketName, objectName);
        try {
            return downloadMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(expires)
                            .build()
            );
        } catch (Exception e) {
            log.error("", e);
            return null;
        }
    }

    /**
     * 获取永久链接
     *
     * @param bucketName 存储桶名称
     * @param path       路径名称
     * @param objectName 存储桶里的对象名称
     * @return 永久链接
     */
    public String getPublicUrl(String bucketName, String path, String objectName) {
        return minioProperties.getDownloadEndpoint() + "/" + bucketName + path + "/" + objectName;
    }

    /**
     * 获取文件上传的链接，最大支持2GB
     *
     * @param uploadUrlParam 文件上传参数
     * @return 文件上传参数
     */
    public Map<String, String> getUploadUrl(UploadUrlParam uploadUrlParam) {
        PostPolicy policy = new PostPolicy(minioProperties.getBucket(), ZonedDateTime.now().plusDays(uploadUrlParam.getExpireDays()));
        try {
            // Add condition that 'key' (object name) equals to 'my-objectname'.
            policy.addEqualsCondition("key", uploadUrlParam.getObjectName());
            // Add condition that 'Content-Type' starts with 'image/'.
            if (StrUtil.isNotBlank(uploadUrlParam.getContentType())) {
                policy.addStartsWithCondition("Content-Type", uploadUrlParam.getContentType());
            }
            // Add condition that 'content-length-range' is between 64kiB to 2GB.
            policy.addContentLengthRangeCondition(64*1024L, 2 * 1024 * 1024 * 1024L);
            return uploadMinioClient.getPresignedPostFormData(policy);
        } catch (Exception e) {
            log.error("", e);
        }
        return new HashMap<>();
    }


}
